﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using Gwn.Compatibility;
using Microsoft.Practices.Unity;

namespace Microsoft.Practices.Mobile.ContainerModel
{
	public sealed partial class Container : IUnityContainer
	{
        private readonly Stack<WeakReference> _disposables = new Stack<WeakReference>();
        private readonly Stack<Container> _childContainers = new Stack<Container>();
        private readonly Dictionary<ServiceKey, ServiceEntry> _services = new Dictionary<ServiceKey, ServiceEntry>();

        public Owner DefaultOwner { get; set; }
        public ReuseScope DefaultReuse { get; set; }

		public Container()
		{
			_services[new ServiceKey(typeof(Func<Container, Container>), null)] =
				new ServiceEntry<Container, Func<Container, Container>>(c => c)
				{
					Container = this,
					Instance = this,
					Owner = Owner.External,
					Reuse = ReuseScope.Container,
				};
		}

        //----------------------------------------------------------------------------
        // IUnityContainer 
        //----------------------------------------------------------------------------
        public class ServiceType
        {
            public Type From { get; set;}
            public Type To { get; set; }

            public ServiceType (Type from, Type to)
            {
                From = from;
                To = to;
            }
        }

	    private Dictionary<Type, object> _serviceTypes = new Dictionary<Type, object>();
        public Dictionary<Type, object> ServiceTypes
        { get { return _serviceTypes; } }

        public IUnityContainer RegisterType<TService, TImplementation>() { return RegisterType<TService, TImplementation>(null, null); }
        public IUnityContainer RegisterType<TService, TImplementation>(LifetimeManager lifetimeManager) { return RegisterType<TService, TImplementation>(null, lifetimeManager); }
        public IUnityContainer RegisterType<TService, TImplementation>(string name, LifetimeManager lifetimeManager)
        {
            var entry = RegisterImpl<TService, Func<Container, TService>>(name,
                container=>(TService)Activator.CreateInstance(typeof(TImplementation)));

            // emulate unity - create new instance by default
            var scope = lifetimeManager == null ? ReuseScope.None : lifetimeManager.ReuseScope;
            entry.ReusedWithin(scope).OwnedBy(Owner.External);
            return this;
        }

        public IUnityContainer RegisterType(Type from, Type to) { return RegisterType(from, to, (string)null); }
        public IUnityContainer RegisterType(Type from, Type to, LifetimeManager lifetimeManager) { return RegisterType(from, to, null, lifetimeManager); }
        public IUnityContainer RegisterType(Type from, Type to, string name) { return RegisterType(from, to, null, null); }
        public IUnityContainer RegisterType(Type from, Type to, string name, LifetimeManager lifetimeManager, params InjectionMember[] injectionMembers) { return RegisterType(from, to, name, lifetimeManager); }
        public IUnityContainer RegisterType(Type from, Type to, string name, LifetimeManager lifetimeManager)
        {
            return RegisterServ(from, to, name, lifetimeManager);
        }

        public IUnityContainer RegisterInstance(object instance) { return RegisterInstance(null, null, instance, null); }
        public IUnityContainer RegisterInstance(Type t, string name, object instance, LifetimeManager lifetime)
        {
            RegisterServ(t, instance, name, lifetime );
            return this;
        }

        public object Resolve(Type t) {return Resolve(t, null, null); }
        public object Resolve(Type t, string name, params ResolverOverride[] resolverOverrides)
        { return ResolveType(t, name); }

        public IUnityContainer AddExtension(UnityContainerExtension extension)
        { return this; }

        private Container _parent;
        public IUnityContainer Parent { get { return _parent; } }

        public IUnityContainer CreateChildContainer()
        {
            return CreateChildContainerModel();
        }

        /// <summary>
        /// This overload is not part of the IUnity interface
        /// </summary>
        /// <param name="classBase"></param>
        public void BuildUp(object classBase)
        {
            if (classBase == null || classBase is IUnityContainer)
                return;

            foreach (var entryProperty in classBase.GetType().GetProperties())
            {
                // don't process indexers
                if (!entryProperty.PropertyType.IsGenericType && entryProperty.ToString().Contains("["))
                    continue;

                if (entryProperty.PropertyType.Name.Equals("IDictionary`2"))
                    continue;

                var value = entryProperty.GetValue(classBase, null);
                try
                {
                    if (value == null)
                    {
                        var attributeList = entryProperty.GetCustomAttributes(true);
                        if (attributeList != null && attributeList.Length > 0)
                        {
                            // There could be multiple attributes - is there a [Dependency] ?
                            var attribute = attributeList
                                .FirstOrDefault(a => a.ToString()
                                                .Contains("Microsoft.Practices.Unity.DependencyAttribute"));

                            // Only resolve dependency attributes
                            if (attribute != null)
                            {
                                object data = ResolveType(entryProperty.PropertyType);
                                if (entryProperty.PropertyType.Name.Contains("IUnityContainer"))
                                    entryProperty.SetValue(classBase, this, null);
                                else
                                    entryProperty.SetValue(classBase, data, null);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    var msg = string.Format("ERROR during BuildUp of dependency property {0} ({1})\r\n\t{2}\r\n\t{3}", 
                        entryProperty.Name, entryProperty.PropertyType.Name,
                        ex.Message, ex.StackTrace);

                    Debug.WriteLine(msg);
                }
            }
        }
        #region --[ IUnityContainer not supported ]----------------------------------------

        public IEnumerable<object> ResolveAll(Type t, params ResolverOverride[] resolverOverrides)
        { return null; }

        public object BuildUp(Type t, object existing, string name, params ResolverOverride[] resolverOverrides)
        {
            BuildUp(existing);
            return existing;
        }

        public void Teardown(object o)
        { throw new NotImplementedException(); }

        public object Configure(Type configurationInterface)
        {
            return null;
        }

        public IUnityContainer RemoveAllExtensions()
        { throw new NotImplementedException(); }

	    private List<ContainerRegistration> _registrations = new List<ContainerRegistration>();
        public IEnumerable<ContainerRegistration> Registrations
	    {
            get { return _registrations; }
            set { }
	    }

        #endregion //--------------------------------------------------------------------------------------------------------------

        // End of IUnityContainer :::::::::::::::::::::::::::::::::::::::::::::::::
        //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

        //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
        // ContainerModel code

        public object ResolveType(Type type)
        {
            return ResolveImpl(type, null, true);
        }
        public object ResolveType(Type type, string name)
        {
            return ResolveImpl(type, name, true);
        }

        public Container CreateChildContainerModel()
        {
            var child = new Container { _parent = this };
            child.Register<IUnityContainer>(child);

            _childContainers.Push(child);
            return child;
        }

        public void Dispose()
        {
            while (_disposables.Count > 0)
            {
                var wr = _disposables.Pop();
                var disposable = (IDisposable)wr.Target;
                if (wr.IsAlive)
                    disposable.Dispose();
            }
            while (_childContainers.Count > 0)
            {
                _childContainers.Pop().Dispose();
            }
        }

        //----------------------------------------------------------------------------
      
        public void Register<TService>(TService instance)
        {
            Register(null, instance);
        }

	    public void Register<TService>(string name, TService instance)
        {
            var entry = RegisterImpl<TService, Func<Container, TService> >(name, null);
			
			// Set sensible defaults for instance registration.
			entry.ReusedWithin(ReuseScope.Hierarchy).OwnedBy(Owner.External);
			entry.InitializeInstance(instance);
		}

        private ServiceEntry<TService, TFunc> RegisterImpl<TService, TFunc>(string name, TFunc factory)
        {
            if (typeof(TService) == typeof(Container))
                throw new ArgumentException(Properties.Resources.Registration_CantRegisterContainer);

            var entry = new ServiceEntry<TService, TFunc>(factory)
            {
                Container = this,
                Reuse = DefaultReuse,
                Owner = DefaultOwner
            };

            var key = new ServiceKey(typeof(TFunc), name);
            _services[key] = entry;
            return entry;
        }

        
        
        public TService ResolveNamed<TService>(bool isErrorIfMissing)
        {
            return ResolveImpl<TService>(null, false);
        }

        public TService Resolve<TService>(bool isErrorIfMissing)
        {
            return ResolveNamed<TService>(isErrorIfMissing);
        }


        /* All ResolveImpl are essentially equal, except for the type of the factory 
         * which is "hardcoded" in each implementation. This slight repetition of 
         * code gives us a bit more of perf. gain by avoiding an intermediate 
         * func/lambda to call in a generic way as we did before.
         */

        private object ResolveImpl(Type type, string name, bool throwIfMissing)
        {
            var thisType = typeof(Container);
            var methodInfo = thisType.GetMethod("ResolveImpl");

            var qualifiedName = type.AssemblyQualifiedName;
            var actualType = Type.GetType(qualifiedName);

            var constructed = methodInfo.MakeGenericMethod(actualType);
            object[] args = { name, throwIfMissing };

            var result = constructed.Invoke(this, args);
            BuildUp(result);
            return result;
        }

        public TService ResolveImpl<TService>(string name, bool throwIfMissing)
		{
            // Support for type and object registrations
            ServiceEntry entryExtra;

			// Would throw if missing as appropriate.
			var entry = GetEntry<TService, Func<Container, TService>>(name, throwIfMissing, out entryExtra);
			
            // If entry==null then default behavior for ContainerModel did not locate
            // the service entry.  We added functionality to support Types/objects which will
            // be output to the entryExtra field - if it is not null then we have a type/object
            // stored in service entry so we'll need to process as applicable
            if (entry==null && entryExtra != null)
            {
                if (entryExtra is ServiceEntry<Container, Func<Container, Type>>)
                {
                    var entryRef = entryExtra as ServiceEntry<Container, Func<Container, Type>>;
                    Type inst = null;
                   
                    if (entryRef.InstanceObject == null)
                    {
                        inst = entryRef.Factory(entryRef.Container);
                        entryRef.InitializeInstance(inst);
                    }
                    // If the instance object is null at this point we will be 
                    // creating one (CreateInstance) however we need to set a 
                    // flag so that we'll know we just created it (didn't receive
                    // and instance) so that we can buildup after creation
                    var isInstance = entryRef.InstanceObject != null;
                    var objectRef = (TService)(entryRef.InstanceObject ?? Activator.CreateInstance(inst));
                    if (!isInstance)
                        BuildUp(objectRef);

                    return objectRef;
                }

                if (entryExtra is ServiceEntry<Container, Func<Container, object>>)
                {
                    var entryObj = entryExtra as ServiceEntry<Container, Func<Container, object>>;
                    var result = entryObj.Factory(entryObj.Container);
                    if (result != null)
                        entryObj.InitializeInstance(result);

                    return (TService)result;
                }
            }

            // Standard behavior follows
            if (entry != null)
            {
                var instance = entry.Instance;
                if (instance == null)
                {
                    instance = entry.Factory(entry.Container);
                    entry.InitializeInstance(instance);
                }
                return instance;
            }

            // If TService is not an interface then we'll construct it
            if (!typeof(TService).IsInterface)
            {
                try
                {
                    var newInstance = Activator.CreateInstance<TService>();
                    BuildUp(newInstance);
                    return newInstance;
                }
                catch (Exception ex)
                {
                    var message = string.Format("\r\n\r\nERROR creating instance of {0} (info:Container requires classes to have empty constructor)\r\n{1}{2}", 
                        typeof(TService).FullName,
                        ex.Message, ex.StackTrace);

                    Debug.WriteLine(message);

                    throw new NotSupportedException(message);
                }
            }

            return default(TService);
		}

		#region ResolveImpl

		private TService ResolveImpl<TService, TArg>(string name, bool throwIfMissing, TArg arg)
		{
			// Would throw if missing as appropriate.
			var entry = GetEntry<TService, Func<Container, TArg, TService>>(name, throwIfMissing);
			// Return default if not registered and didn't throw above.
			if (entry == null)
				return default(TService);

			TService instance = entry.Instance;
			if (instance == null)
			{
				instance = entry.Factory(entry.Container, arg);
				entry.InitializeInstance(instance);
			}

			return instance;
		}

		private TService ResolveImpl<TService, TArg1, TArg2>(string name, bool throwIfMissing, TArg1 arg1, TArg2 arg2)
		{
			// Would throw if missing as appropriate.
			var entry = GetEntry<TService, Func<Container, TArg1, TArg2, TService>>(name, throwIfMissing);
			// Return default if not registered and didn't throw above.
			if (entry == null)
				return default(TService);

			TService instance = entry.Instance;
			if (instance == null)
			{
				instance = entry.Factory(entry.Container, arg1, arg2);
				entry.InitializeInstance(instance);
			}

			return instance;
		}

		private TService ResolveImpl<TService, TArg1, TArg2, TArg3>(string name, bool throwIfMissing, TArg1 arg1, TArg2 arg2, TArg3 arg3)
		{
			// Would throw if missing as appropriate.
			var entry = GetEntry<TService, Func<Container, TArg1, TArg2, TArg3, TService>>(name, throwIfMissing);
			// Return default if not registered and didn't throw above.
			if (entry == null)
				return default(TService);

			TService instance = entry.Instance;
			if (instance == null)
			{
				instance = entry.Factory(entry.Container, arg1, arg2, arg3);
				entry.InitializeInstance(instance);
			}

			return instance;
		}

		private TService ResolveImpl<TService, TArg1, TArg2, TArg3, TArg4>(string name, bool throwIfMissing, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4)
		{
			// Would throw if missing as appropriate.
			var entry = GetEntry<TService, Func<Container, TArg1, TArg2, TArg3, TArg4, TService>>(name, throwIfMissing);
			// Return default if not registered and didn't throw above.
			if (entry == null)
				return default(TService);

			TService instance = entry.Instance;
			if (instance == null)
			{
				instance = entry.Factory(entry.Container, arg1, arg2, arg3, arg4);
				entry.InitializeInstance(instance);
			}

			return instance;
		}

		private TService ResolveImpl<TService, TArg1, TArg2, TArg3, TArg4, TArg5>(string name, bool throwIfMissing, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5)
		{
			// Would throw if missing as appropriate.
			var entry = GetEntry<TService, Func<Container, TArg1, TArg2, TArg3, TArg4, TArg5, TService>>(name, throwIfMissing);
			// Return default if not registered and didn't throw above.
			if (entry == null)
				return default(TService);

			TService instance = entry.Instance;
			if (instance == null)
			{
				instance = entry.Factory(entry.Container, arg1, arg2, arg3, arg4, arg5);
				entry.InitializeInstance(instance);
			}

			return instance;
		}

		private TService ResolveImpl<TService, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6>(string name, bool throwIfMissing, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6)
		{
			// Would throw if missing as appropriate.
			var entry = GetEntry<TService, Func<Container, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TService>>(name, throwIfMissing);
			// Return default if not registered and didn't throw above.
			if (entry == null)
				return default(TService);

			TService instance = entry.Instance;
			if (instance == null)
			{
				instance = entry.Factory(entry.Container, arg1, arg2, arg3, arg4, arg5, arg6);
				entry.InitializeInstance(instance);
			}

			return instance;
		}

		#endregion

        #region Support methods
        internal void TrackDisposable(object instance)
        {
            _disposables.Push(new WeakReference(instance));
        }

        private ServiceEntry<TService, TFunc> GetEntry<TService, TFunc>(string serviceName, bool throwIfMissing)
        {
            ServiceEntry entryType;
            return GetEntry<TService,TFunc>(serviceName, throwIfMissing, out entryType);
        }

        private ServiceEntry<TService, TFunc> GetEntry<TService, TFunc>(string serviceName, bool throwIfMissing, out ServiceEntry entryRef)
        {
            entryRef = null;

            var key = new ServiceKey(typeof(TFunc), serviceName);
            ServiceEntry entry = null;
            Container container = this;

            // Go up the hierarchy always for registrations.
            while (!container._services.TryGetValue(key, out entry) && container._parent != null)
            {
                container = container._parent;
            }

            if (entry == null)
            {
                var key2 = new ServiceKey(typeof(TService), serviceName);
                while (!container._services.TryGetValue(key2, out entry) && container._parent != null)
                    container = container._parent;

                if (entry != null)
                {
                    entryRef = entry;
                    return null;
                }
            }

            if (entry != null)
            {
                if (entry.Reuse == ReuseScope.Container && entry.Container != this)
                {
                    entry = ((ServiceEntry<TService, TFunc>)entry).CloneFor(this);
                    _services[key] = entry;
                }
            }
            else if (throwIfMissing)
            {
                // Only throw if TService is an interface, otherwise
                // we can create an instance upon exiting.
                if (typeof(TService).IsInterface)
                    ThrowMissing<TService>(serviceName);
            }

            return (ServiceEntry<TService, TFunc>)entry;
        }

        private static TService ThrowMissing<TService>(string serviceName)
        {
            if (serviceName == null)
                throw new ResolutionException(typeof(TService));
            else
                throw new ResolutionException(typeof(TService), serviceName);
        }

        private void ThrowIfNotRegistered<TService, TFunc>(string name)
        {
            GetEntry<TService, TFunc>(name, true);
        }
        #endregion

        #region IUnityContainer implementation support methods

        private IUnityContainer RegisterServ(Type from, object instance, string name, LifetimeManager lifetimeManager)
        {
            if (from == null)
                from = instance.GetType();

            Func<Container, object> func = c => instance;

            var entry = new ServiceEntry<Container, Func<Container, object>>(func)
            {
                Container = this,
                Reuse = DefaultReuse,
                Owner = DefaultOwner
            };

            entry.InitializeInstance(instance);

            _registrations.Add(new ContainerRegistration(from, name, null));

            var key = new ServiceKey(from, name);
            _services[key] = entry;

            // emulate unity - create new instance by default
            var scope = lifetimeManager == null ? ReuseScope.None : lifetimeManager.ReuseScope;
            entry.ReusedWithin(scope).OwnedBy(Owner.External);
            return this;
        }

        private IUnityContainer RegisterServ(Type from, Type to, string name, LifetimeManager lifetimeManager)
        {
            Func<Container, Type> func = c => to;

            if (from == null)
                from = to;

            var entry = new ServiceEntry<Container, Func<Container, Type>>(func)
            {
                Container = this,
                Reuse = DefaultReuse,
                Owner = DefaultOwner
            };

            _registrations.Add(new ContainerRegistration(from, name, null));

            var key = new ServiceKey(from, name);
            _services[key] = entry;

            // emulate unity - create new instance by default
            var scope = lifetimeManager == null ? ReuseScope.None : lifetimeManager.ReuseScope;
            entry.ReusedWithin(scope).OwnedBy(Owner.External);
            return this;
        }
        #endregion 

        #region Utility methods

        private static ServiceKey ServiceKey<TService>(TService type, string name)
            where TService : Type
        {
            //::::::::::::::::::::::::::::::::::::::::::::
            // TService needs to be IFoo (not Type)
            // Key can then be found in _services
            //::::::::::::::::::::::::::::::::::::::::::::

            var thisType = typeof(Container);
            var methodInfo = thisType.GetMethod("MakeFunc");

            var qualifiedName = type.AssemblyQualifiedName;
            var actualType = Type.GetType(qualifiedName);

            var constructed = methodInfo.MakeGenericMethod(actualType);
            object[] args = { actualType };
            var result = constructed.Invoke(null, args);

            return (ServiceKey)result;
        }

        public static ServiceKey MakeFunc<TType>(object type)
        {
            Func<Container, TType> func = container => (TType)type;
            var serviceKey = new ServiceKey(typeof(Func<Container, TType>), null);
            return serviceKey;
        }


        private static void DisplayGenericMethodInfo(MethodInfo mi)
        {
            Debug.WriteLine("\r\n{0}", mi);

            Debug.WriteLine("\tIs this a generic method definition? {0}",
                              mi.IsGenericMethodDefinition);

            Debug.WriteLine("\tIs it a generic method? {0}",
                              mi.IsGenericMethod);

            Debug.WriteLine("\tDoes it have unassigned generic parameters? {0}",
                              mi.ContainsGenericParameters);

            // If this is a generic method, display its type arguments.
            //
            if (mi.IsGenericMethod)
            {
                Type[] typeArguments = mi.GetGenericArguments();

                Debug.WriteLine("\tList type arguments ({0}):",
                                  typeArguments.Length);

                foreach (Type tParam in typeArguments)
                {
                    // IsGenericParameter is true only for generic type
                    // parameters.
                    //
                    if (tParam.IsGenericParameter)
                    {
                        Debug.WriteLine("\t\t{0}  parameter position {1}" +
                                          "\n\t\t   declaring method: {2}",
                                          tParam,
                                          tParam.GenericParameterPosition,
                                          tParam.DeclaringMethod);
                    }
                    else
                    {
                        Debug.WriteLine("\t\t{0}", tParam);
                    }
                }
            }
        }
        #endregion

    }
}
